defmodule EliPhxPlay.ChatSchema.GroupMembers do
  @moduledoc """
  群聊成员数据模型

  管理群聊中成员的基本信息，包括角色权限、群内昵称、禁言状态等。
  支持三种角色：群主(owner)、管理员(admin)、普通成员(member)。
  """

  use Ecto.Schema
  import Ecto.Changeset

  # 设置主键为 binary_id 类型，自动生成 UUID
  @primary_key {:member_id, :binary_id, autogenerate: true}

  # 设置外键默认类型为 binary_id
  @foreign_key_type :binary_id

  schema "group_members" do
    # 群内角色：owner(群主)、admin(管理员)、member(普通成员)
    # 默认为普通成员
    field :role, :string, default: "member"

    # 用户在该群聊中的自定义昵称，可选字段
    field :group_nickname, :string

    # 是否被禁言标识，默认为 false（未禁言）
    field :is_muted, :boolean, default: false

    # 禁言到期时间，只有在 is_muted 为 true 时才有意义
    field :mute_until, :naive_datetime

    # 关联到群聊表，建立多对一关系
    # 每个群成员记录属于一个特定的群聊
    belongs_to :group, EliPhxPlay.ChatSchema.Groups,
      foreign_key: :group_id,    # 当前表中的外键字段名
      references: :group_id,     # 目标表中被引用的字段名
      type: :binary_id           # 外键数据类型

    # 关联到用户表，建立多对一关系
    # 每个群成员记录属于一个特定的用户
    belongs_to :user, EliPhxPlay.ChatSchema.Users,
      foreign_key: :user_id,     # 当前表中的外键字段名
      references: :user_id,      # 目标表中被引用的字段名
      type: :binary_id           # 外键数据类型

    # 自动添加 inserted_at 和 updated_at 时间戳字段
    timestamps()
  end

  @doc """
  创建群成员的 changeset

  用于验证和转换群成员数据，确保数据的完整性和业务规则的正确性。

  ## 参数
  - `group_members`: GroupMembers 结构体或 changeset
  - `attrs`: 包含要更新字段的映射

  ## 验证规则
  - 必填字段：role、group_id、user_id
  - 角色限制：只能是 owner、admin、member 之一
  - 群昵称长度：最多 50 个字符
  - 禁言逻辑：禁言状态与到期时间必须一致
  - 唯一性：同一用户不能重复加入同一群聊

  ## 示例
      iex> changeset(%GroupMembers{}, %{role: "member", group_id: group_id, user_id: user_id})
      #Ecto.Changeset<...>
  """
  def changeset(group_members, attrs) do
    group_members
    # 将传入的属性映射到对应的字段
    |> cast(attrs, [:role, :group_nickname, :is_muted, :mute_until, :group_id, :user_id])

    # 验证必填字段：角色、群聊ID、用户ID 是必需的
    |> validate_required([:role, :group_id, :user_id])

    # 验证角色字段只能是预定义的三个值之一
    |> validate_inclusion(:role, ["owner", "admin", "member"],
        message: "角色必须是 owner、admin 或 member")

    # 验证群昵称长度不超过 50 个字符
    |> validate_length(:group_nickname, max: 50,
        message: "群昵称不能超过50个字符")

    # 执行自定义的禁言逻辑验证
    |> validate_mute_logic()

    # 确保同一用户不能重复加入同一群聊（数据库级别的唯一约束）
    |> unique_constraint([:group_id, :user_id],
        message: "用户已经在该群聊中")
  end


  # 验证禁言逻辑的一致性

  # 确保禁言状态 (is_muted) 与禁言到期时间 (mute_until) 之间的逻辑一致性：
  # - 如果用户被禁言，必须设置到期时间
  # - 如果用户未被禁言，不应该有到期时间
  # - 禁言到期时间不能早于当前时间

  # ## 参数
  # - `changeset`: 包含群成员数据的 changeset

  # ## 返回值
  # - 有效的 changeset 或包含错误信息的 changeset
  defp validate_mute_logic(changeset) do
    # 从 changeset 中获取禁言状态和到期时间
    is_muted = get_field(changeset, :is_muted)
    mute_until = get_field(changeset, :mute_until)

    cond do
      # 情况1：用户被标记为禁言，但没有设置到期时间
      is_muted && is_nil(mute_until) ->
        add_error(changeset, :mute_until, "禁言时必须设置到期时间")

      # 情况2：用户未被禁言，但设置了到期时间
      !is_muted && !is_nil(mute_until) ->
        add_error(changeset, :is_muted, "设置了禁言时间但未标记为禁言状态")

      # 情况3：禁言到期时间早于当前时间（无效的时间设置）
      !is_nil(mute_until) && NaiveDateTime.compare(mute_until, NaiveDateTime.utc_now()) == :lt ->
        add_error(changeset, :mute_until, "禁言到期时间不能早于当前时间")

      # 情况4：所有验证都通过
      true ->
        changeset
    end
  end
end
